﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Windows.Media;

namespace ULSDeobfuscator {
    public class ColorCoder {

        private Predicate<LogEntry> _doTestColorExpression;
        private Action<LogEntry> _doSetForeground;
        private Action<LogEntry> _doSetBackground;

        private SolidColorBrush _foregroundBrush;
        private SolidColorBrush _backgroundBrush;

        private const string RootParameterName = "le";

        public bool IsForegroundSet { get; set; }
        public bool IsBackgroundSet { get; set; }

        public ColorCoder(ColorRule cr) {

            ComputeCoderMethod(cr);
        }

        private void ComputeCoderMethod(ColorRule cr) {
            
            if (cr.ForegroundColor == null && cr.BackgroundColor == null) {
                throw new InvalidOperationException("Both foreground and background color cannot be null");
            }

            ParameterExpression rootParameter = Expression.Parameter(typeof(LogEntry), RootParameterName);

            List<Expression> singlePropertyExpressions = new List<Expression>();

            // processes 
            if (cr.Processes.Count > 0) {

                Expression attribute = Expression.Property(rootParameter, "Process");

                Expression combined = GetProcessExpression(attribute, cr.Processes[0]);

                for (int i = 1; i < cr.Processes.Count; i++) {

                    combined = Expression.Or(combined, GetProcessExpression(attribute, cr.Processes[i]));
                }

                singlePropertyExpressions.Add(combined);
            }

            // areas
            if (cr.Areas.Count > 0) {
                
                Expression attribute = Expression.Property(rootParameter, "Area");

                Expression combined = GetAreaExpression(attribute, cr.Areas[0]);

                for (int i = 1; i < cr.Areas.Count; i++) {

                    combined = Expression.Or(combined, GetAreaExpression(attribute, cr.Areas[i]));
                }

                singlePropertyExpressions.Add(combined);
            }

            // categories
            if (cr.Categories.Count > 0) {

                Expression attribute = Expression.Property(rootParameter, "Category");

                Expression combined = GetCategoryExpression(attribute, cr.Categories[0]);

                for (int i = 1; i < cr.Categories.Count; i++) {

                    combined = Expression.Or(combined, GetCategoryExpression(attribute, cr.Categories[i]));
                }

                singlePropertyExpressions.Add(combined);
            }

            // levels
            if (cr.Levels.Count > 0) {

                Expression attribute = Expression.Property(rootParameter, "Level");

                Expression combined = GetLevelExpression(attribute, cr.Levels[0]);

                for (int i = 1; i < cr.Levels.Count; i++) {

                    combined = Expression.Or(combined, GetLevelExpression(attribute, cr.Levels[i]));
                }

                singlePropertyExpressions.Add(combined);
            }

            // Event IDs
            if (cr.EventIDs.Count > 0) {
                
                Expression attribute = Expression.Property(rootParameter, "EventID");

                Expression combined = GetEventIDExpression(attribute, cr.EventIDs[0]);

                for (int i = 1; i < cr.EventIDs.Count; i++) {

                    combined = Expression.Or(combined, GetEventIDExpression(attribute, cr.EventIDs[i]));
                }

                singlePropertyExpressions.Add(combined);
            }

            // message
            if (! String.IsNullOrEmpty(cr.Message)) {
                
                Expression attribute = Expression.Property(rootParameter, "Message");
                Expression val = Expression.Constant(cr.Message, typeof(string));
                Expression condition = Expression.Call(attribute, "Contains", null, val);

                singlePropertyExpressions.Add(condition);
            }

            // correlation

            if (! String.IsNullOrEmpty(cr.Correlation)) {
                
                Expression attribute = Expression.Property(rootParameter, "Correlation");
                Expression val = Expression.Constant(cr.Correlation, typeof(string));
                Expression condition = Expression.Call(attribute, "Contains", null, val);

                singlePropertyExpressions.Add(condition);
            }

            // multi-line
            if (cr.IsMultiLine) {

                // there may be an easier way to do this..
                Expression attribute = Expression.Property(rootParameter, "IsMultiLine");
                Expression val = Expression.Constant(true, typeof(bool));
                Expression condition = Expression.Equal(attribute, val);

                singlePropertyExpressions.Add(condition);
            }

            // multi-line
            if (cr.HasAnnotation) {

                Expression condition = Expression.Property(rootParameter, "HasAnnotation");

                singlePropertyExpressions.Add(condition);
            }

            if (singlePropertyExpressions.Count == 0) {
                throw new InvalidOperationException("Rule " + cr.Name + " has no condition");
            }

            Expression multiPropertyExpressions = singlePropertyExpressions[0];
            for (int i = 1; i < singlePropertyExpressions.Count; i++ ) {
                multiPropertyExpressions = Expression.And(singlePropertyExpressions[i], multiPropertyExpressions);
            }

            _doTestColorExpression = Expression.Lambda<Predicate<LogEntry>>(multiPropertyExpressions, rootParameter).Compile();

            if (cr.ForegroundColor == null) {
                _doSetForeground = delegate {};
                IsForegroundSet = false;
            } else {
                _foregroundBrush = new SolidColorBrush(cr.ForegroundColor.Value);
                _foregroundBrush.Freeze(); // this improves perf A LOT. Try to remove it if you don't believe me :-)
                _doSetForeground = le => le.ForegroundBrush = _foregroundBrush;
                IsForegroundSet = true;
            }

            if (cr.BackgroundColor == null) {
                _doSetBackground = delegate { };
                IsBackgroundSet = false;
            } else {
                _backgroundBrush = new SolidColorBrush(cr.BackgroundColor.Value);
                _backgroundBrush.Freeze();
                _doSetBackground = le => le.BackgroundBrush = _backgroundBrush;
                IsBackgroundSet = true;
            }
        }

        private static Expression GetProcessExpression(Expression processAccess, string value) {

            Expression condition;

            if (value == ColorRule.AnyWorkerProcess) {

                Expression val = Expression.Constant(ColorRule.WorkerProcessPrefix, typeof(string));
                condition = Expression.Call(processAccess, "StartsWith", null, val);
            } else {
                Expression val = Expression.Constant(value, typeof (string));
                condition = Expression.Equal(processAccess, val);
            }

            return condition;

        }

        private static Expression GetAreaExpression(Expression areaAccess, string value) {
            return GetSimpleExpression(areaAccess, value);
        }

        private static Expression GetCategoryExpression(Expression areaAccess, string value) {
            return GetSimpleExpression(areaAccess, value);
        }

        private static Expression GetLevelExpression(Expression areaAccess, string value) {
            return GetSimpleExpression(areaAccess, value);
        }

        private static Expression GetEventIDExpression(Expression areaAccess, string value) {
            return GetSimpleExpression(areaAccess, value);
        }

        private static Expression GetSimpleExpression(Expression propertyAccess, string value) {

            Expression val = Expression.Constant(value, typeof (string));
            return Expression.Equal(propertyAccess, val);
        }

        // more for unit test than the actual program
        public bool DoesRuleMatch(LogEntry le) {
            return _doTestColorExpression(le);
        }

        /// <summary>
        /// Set the colors according to the rule, if the rule matches
        /// </summary>
        /// <param name="le">The entry to consider</param>
        /// <returns>True if the rule matched</returns>
        public bool SetColors(LogEntry le) {
            
            if (_doTestColorExpression(le)) {
                _doSetForeground(le);
                _doSetBackground(le);

                return true;
            }

            return false;
        }
    }
}
